#!/usr/bin/env python

# An example for a daemon process to loop endlessly taking as much data
# from a currentcost device as it can and storing that data in rrd
# files and/or a plain text file.
# pir 2010/03/18

# Global variables
BASE = '/usr/local/power'    # A directory to store everything underneath.
PORT = '/dev/cu.usbserial'   # The port connected to the currentcost.
BAUD = 57600                 # 9600 for a classic, 57600 for a cc128.
RRD = '%s/rrd/cc-%%d.rrd' % BASE # rrdtool file, this need to be created first.
LOG = '%s/var/output.txt' % BASE # Plain text output file.
# The number of seconds to keep running without a successful read.
DIE_TIME = 300
DEBUG = False
#DEBUG = True

import sys
# Where to find pycurrentcost.py
sys.path.append('%s/lib' % BASE)
import pycurrentcost

import logging
import logging.handlers
import os
import rrdtool
import signal
import syslog
import time


class TimeoutException(Exception): 
  pass 

def timeout_handler(signum, frame):
  raise TimeoutException()


def RrdUpdate(rrdfile, updatestr):
  """Take an rrd file and a string to update to it."""
  if rrdfile:
    try:
      rrdtool.update(rrdfile, updatestr)
    except rrdtool.error, e:
      syslog.syslog(syslog.LOG_ERR, 'Error updating RRD %s: %s' % (rrdfile, e))

def InitSysLog(debug=False,
               facility=logging.handlers.SysLogHandler.LOG_DAEMON,
               format='%(filename)s[%(process)d]: %(levelname)s %(message)s'):
  """Set up the logging module to log to syslog."""

  platform = sys.platform
  if platform == "darwin":
    # Apple made 10.5 more secure by disabling network syslog:
    address = "/var/run/syslog"
  elif platform.startswith('linux') or platform.startswith('freebsd'):
    address = "/dev/log"
  else:
    address = ('localhost', 514)
  syslog = logging.handlers.SysLogHandler(address, facility=facility)

  logger = logging.getLogger()
  if debug:
    logger.setLevel(logging.DEBUG)
  else:
    logger.setLevel(logging.INFO)

  formatter = logging.Formatter(format)
  syslog.setFormatter(formatter)
  logger.addHandler(syslog)


if __name__ == '__main__':

  # Init syslog.
  InitSysLog(debug=DEBUG)

  # Init the CurrentCost unit.
  cc = pycurrentcost.CurrentCostReader(port=PORT, baudrate=BAUD)

  # Make sure our output is readable if it didn't exist already.
  os.umask(0022)
  # Open the plain text logfile.
  if LOG:
    logfile = open(LOG, 'a')

  # Set an initial update time.
  updatetime = int(time.time())

  # Loop and take readings.
  while True:

    watts = None

    # Timeout the command.
    old_handler = signal.signal(signal.SIGALRM, timeout_handler) 
    signal.alarm(12) # trigger alarm in 12 seconds

    try:
      reading = cc.get_reading()
    except pycurrentcost.CurrentCostException, e:
      logging.error('Error from get_reading: %s' % e)
    except TimeoutException:
      logging.error('Timeout from get_reading')

    else:
      sensor_num = int(reading.sensor_num)
      watts = int(reading.channels[1]['watts'])

      # The meters really aren't that sensitive for small power amounts.
      if watts < 5:
        watts = 0

      updatetime = int(time.time())

    finally:
      signal.signal(signal.SIGALRM, old_handler) 

    signal.alarm(0)

    # If we got an update, push data out.
    if watts != None:
      updatestr = str(u'%d:%s:%s' % (updatetime, watts, reading.temperature))
      logging.debug('Got data from sensor %d: %s' % (sensor_num, updatestr))

      # Write it all to a flat text file.
      if LOG:
        logfile.write('%d %s\n' % (sensor_num, updatestr))
        logfile.flush()

      # Log to a specific rrd file
      if RRD:
        RrdUpdate(RRD % sensor_num, updatestr)

    # Check if we've had a recent update or not.
    if updatetime + DIE_TIME <= int(time.time()):
      logging.error('No updates for %s seconds, suiciding.' % DIE_TIME)
      sys.exit(1)
